﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using Xml = System.Xml;

namespace PluginFramework
{
    public abstract class PluginLoader
    {
        #region FindAssembly()
        /// <summary>
        /// Populates the plugin definition file if necessary and returns the Type requested
        /// </summary>
        /// <param name="PluginClass">Unique identifer for this "kind" of plugin</param>
        /// <param name="Class">The class name requested</param>
        /// <param name="Folders">The folders to search through</param>
        /// <param name="PluginTypes">The valid Interfaces</param>
        /// <returns></returns>
        protected static Type FindAssembly(string PluginClass, string Class, string[] Folders, params Type[] PluginTypes)
        {
            // Loop through the folders
            foreach (string Folder in Folders)
            {
                // Load the file (or get a null if there's an error)
                Xml.XmlDocument PluginLibrary = LoadXmlFile(System.IO.Path.Combine(Folder, "plugins.xml"));

                // Create or update the file as necessary
                if (PluginLibrary == null)
                    PluginLibrary = CreatePluginFile(Folder, PluginClass, PluginTypes);
                else
                    PluginLibrary = UpdatePluginFile(Folder, PluginClass, PluginTypes);

                // If we were able to get the file...
                if (PluginLibrary != null)
                {
                    // ... find the appropriate node for this class...
                    Xml.XmlElement LibraryNode = (Xml.XmlElement)PluginLibrary.SelectSingleNode("plugins/active[@type='" + PluginClass + "']/plugin[@name='" + Class + "' or @fullname='" + Class + "']");
                    if (LibraryNode != null)
                    {
                        // ... and load it into a System.Type object and return it
                        System.Reflection.Assembly PluginAssembly = System.Reflection.Assembly.LoadFrom(LibraryNode.InnerText);
                        return PluginAssembly.GetType(LibraryNode.GetAttribute("fullname"));
                    }
                }
            }

            return null;
        }


        protected static Type FindAssembly(string PluginClass, string Class, Type PluginType, params string[] Folders)
        {
            // Pass Through
            return FindAssembly(PluginClass, Class, Folders, PluginType);
        }


        protected static Type FindAssembly(string PluginClass, string Class, string Folder, params Type[] PluginTypes)
        {
            // Pass Through
            return FindAssembly(PluginClass, Class, new string[1] { Folder }, PluginTypes);
        }


        #endregion


        #region FindAllAssemblies()
        /// <summary>
        /// This returns all classes known about by this system
        /// </summary>
        /// <param name="PluginClass">Unique identifer for this "kind" of plugin</param>
        /// <param name="Class">The class name requested</param>
        /// <param name="Folders">The folders to search through</param>
        /// <param name="PluginTypes">The valid Interfaces</param>
        /// <returns></returns>
        protected static Type[] FindAllAssemblies(string PluginClass, string[] Folders, params Type[] PluginTypes)
        {
            // Set up our results list
            System.Collections.ArrayList Result = new System.Collections.ArrayList();

            // Loop through all folders
            foreach (string Folder in Folders)
            {
                Xml.XmlDocument PluginLibrary = LoadXmlFile(System.IO.Path.Combine(Folder, "plugins.xml"));

                // Create or update the plugin file as necessary
                if (PluginLibrary == null)
                    PluginLibrary = CreatePluginFile(Folder, PluginClass, PluginTypes);
                else
                    PluginLibrary = UpdatePluginFile(Folder, PluginClass, PluginTypes);

                if (PluginLibrary != null)
                {
                    Xml.XmlNodeList LibraryList = PluginLibrary.SelectNodes("plugins/active[@type='" + PluginClass + "']/plugin");
                    foreach (Xml.XmlElement LibraryNode in LibraryList)
                    {
                        // Add each class to our results
                        System.Reflection.Assembly PluginAssembly = System.Reflection.Assembly.LoadFrom(LibraryNode.InnerText);
                        Result.Add(PluginAssembly.GetType(LibraryNode.GetAttribute("fullname")));
                    }
                }
            }

            // Convert results to an array at the last moment
            return (System.Type[])Result.ToArray(typeof(System.Type));
        }


        protected static Type[] FindAllAssemblies(string PluginClass, Type PluginType, string[] Folders)
        {
            // Pass Through
            return FindAllAssemblies(PluginClass, Folders, PluginType);
        }


        protected static Type[] FindAllAssemblies(string PluginClass, string Folder, params Type[] PluginTypes)
        {
            // Pass Through
            return FindAllAssemblies(PluginClass, new string[1] { Folder }, PluginTypes);
        }


        #endregion


        #region Private Methods

        /// <summary>
        /// Load up a new plugin file and populate it
        /// </summary>
        private static Xml.XmlDocument CreatePluginFile(string PluginFolder, string PluginClass, Type[] PluginTypes)
        {
            Xml.XmlDocument PluginLibrary = new Xml.XmlDocument();

            PluginLibrary.LoadXml("<plugins><retired/></plugins>");

            AddAssembliesToPluginFile(PluginFolder, PluginClass, PluginLibrary, PluginTypes);

            PluginLibrary.Save(System.IO.Path.Combine(PluginFolder, "plugins.xml"));
            return PluginLibrary;
        }


        /// <summary>
        /// Updates a plugin file, removing all dead classes
        /// </summary>
        private static Xml.XmlDocument UpdatePluginFile(string PluginFolder, string PluginClass, Type[] PluginTypes)
        {
            Xml.XmlDocument PluginLibrary = new Xml.XmlDocument();

            try
            {
                PluginLibrary.Load(System.IO.Path.Combine(PluginFolder, "plugins.xml"));
            }
            catch
            {
                PluginLibrary = CreatePluginFile(PluginFolder, PluginClass, PluginTypes);
            }

            bool FileChanged = false;

            foreach (string PluginFile in System.IO.Directory.GetFiles(PluginFolder, "*.dll"))
            {
                DateTime LastUpdate = new DateTime();
                try
                {
                    LastUpdate = DateTime.Parse(((Xml.XmlElement)PluginLibrary.SelectSingleNode("/plugins/active[@type='" + PluginClass + "']")).GetAttribute("updated"));
                }
                catch
                { }
                if (System.IO.File.GetLastWriteTime(PluginFile) > LastUpdate)
                {
                    foreach (Xml.XmlElement OldAssembly in PluginLibrary.SelectNodes("/plugins/active[@type='" + PluginClass + "']/plugin"))
                    {
                        OldAssembly.ParentNode.RemoveChild(OldAssembly);
                        PluginLibrary.SelectSingleNode("/plugins/retired").AppendChild(OldAssembly);
                    }

                    AddAssembliesToPluginFile(PluginFolder, PluginClass, PluginLibrary, PluginTypes);

                    FileChanged = true;

                    break;
                }
            }

            foreach (Xml.XmlElement OldAssembly in PluginLibrary.SelectNodes("/plugins/retired/plugin"))
            {
                try
                {
                    System.IO.File.Delete(OldAssembly.InnerText);
                    OldAssembly.ParentNode.RemoveChild(OldAssembly);

                    FileChanged = true;
                }
                catch (Exception exx)
                {
                    exx.GetType();
                }
            }

            if (FileChanged)
                PluginLibrary.Save(System.IO.Path.Combine(PluginFolder, "plugins.xml"));

            return PluginLibrary;
        }


        /// <summary>
        /// Adds the active assemblies to a plugin file
        /// </summary>
        private static void AddAssembliesToPluginFile(string PluginFolder, string PluginClass, Xml.XmlDocument PluginLibrary, Type[] PluginTypes)
        {
            if (System.IO.Directory.Exists(PluginFolder))
            {
                foreach (string PluginFile in System.IO.Directory.GetFiles(PluginFolder, "*.dll"))
                {
                    bool FoundOne = false;
                    //string NewFileName = System.IO.Path.GetTempFileName();
                    string FileName = PluginFile.Substring(PluginFile.LastIndexOf("\\") + 1);

                    //System.IO.File.Copy(PluginFile, NewFileName, true);

                    System.Reflection.Assembly PluginAssembly = System.Reflection.Assembly.LoadFrom(FileName);

                    foreach (System.Type NewType in PluginAssembly.GetTypes())
                    {
                        bool Found = false;

                        foreach (System.Type InterfaceType in NewType.GetInterfaces())
                            foreach (System.Type DesiredType in PluginTypes)
                            {
                                if (InterfaceType == DesiredType)
                                {

                                    string ClassName = NewType.Name;
                                    if (NewType.Namespace != null)
                                        ClassName = NewType.Namespace + "." + ClassName;

                                    FoundOne = true;
                                    Xml.XmlElement NewNode = PluginLibrary.CreateElement("plugin");
                                    NewNode.SetAttribute("library", FileName);
                                    NewNode.SetAttribute("interface", DesiredType.Name);
                                    NewNode.SetAttribute("name", NewType.Name);
                                    NewNode.SetAttribute("fullname", ClassName);
                                    NewNode.AppendChild(PluginLibrary.CreateTextNode(FileName));

                                    Xml.XmlElement Parent = (Xml.XmlElement)PluginLibrary.SelectSingleNode("/plugins/active[@type='" + PluginClass + "']");
                                    if (Parent == null)
                                    {
                                        Parent = PluginLibrary.CreateElement("active");
                                        Parent.SetAttribute("type", PluginClass);
                                        PluginLibrary.SelectSingleNode("/plugins").AppendChild(Parent);
                                    }
                                    Parent.AppendChild(NewNode);
                                    Parent.SetAttribute("updated", System.DateTime.Now.ToString());

                                    Found = true;
                                    break;
                                }
                                if (Found) break;
                            }
                    }

                    if (!FoundOne)
                    {
                        Xml.XmlElement NewNode = PluginLibrary.CreateElement("plugin");
                        NewNode.AppendChild(PluginLibrary.CreateTextNode(FileName));

                        PluginLibrary.SelectSingleNode("/plugins/retired").AppendChild(NewNode);
                        PluginLibrary.DocumentElement.SetAttribute("updated", System.DateTime.Now.ToString());
                    }
                }
            }
        }


        /// <summary>
        /// Loads an xml file, returning null if there's any problem
        /// </summary>
        private static Xml.XmlDocument LoadXmlFile(string Path)
        {
            if (System.IO.File.Exists(Path))
            {
                try
                {
                    Xml.XmlDocument Result = new Xml.XmlDocument();
                    Result.Load(Path);
                    return Result;
                }
                catch
                {
                    return null;
                }
            }
            else
                return null;
        }


        #endregion
    }

    /*
    public class PluginLoader
    {
        static private string GetApplicationDirectory()
        {
            return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
        }

        static public void Load()
        {
            string path = GetApplicationDirectory();
            string[] pluginFiles = Directory.GetFiles(path, "*.dll");
            //ipi = new IPlugin[pluginFiles.Length];

            foreach (string file in pluginFiles)
            {
                string args = file.Substring(
                    file.LastIndexOf("\\") + 1,
                    file.IndexOf(".dll") -
                    file.LastIndexOf("\\") - 1);

                Type ObjType = null;
                //IPlugin ipi;
                // load the dll
                try
                {
                    // load it
                    Assembly ass = null;
                    ass = Assembly.Load(args);
                    if (ass != null)
                    {
                        ObjType = ass.GetType(args + ".PlugIn");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                try
                {
                    // OK Lets create the object as we have the Report Type
                    if (ObjType != null)
                    {
                        //ipi[i] = (IPlugin)Activator.CreateInstance(ObjType);
                        //ipi[i].Host = this;
                        //Console.WriteLine("ipi.Add(1,2)=" + ipi[i].Add(1, 2));
                        throw new Exception(ObjType.ToString());
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

                //				Start(assemname);
            }
            //MessageBox.Show(ConfigurationSettings.AppSettings["App"]);
        }

    }
     * */
}
